/*
 * Copyright 2012-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */

package org.spring.cloud.pilot.filewatch;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring.cloud.pilot.util.JarTool;
import org.springframework.util.Assert;

import com.google.common.base.Strings;

/**
 * Watches specific folders for file changes.
 *
 * @author Andy Clement
 * @author Phillip Webb
 * @since 1.3.0
 * @see FileChangeListener
 */
public class FileSystemWatcher {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemWatcher.class);

    private static final long DEFAULT_POLL_INTERVAL = 1000;

    private static final long DEFAULT_QUIET_PERIOD = 400;

    private final List<FileChangeListener> listeners = new ArrayList<FileChangeListener>();

    private final boolean daemon;

    private final long pollInterval;

    private final long quietPeriod;

    private final AtomicInteger remainingScans = new AtomicInteger(-1);

    private final Map<File, FolderSnapshot> folders = new HashMap<File, FolderSnapshot>();

    private Thread watchThread;

    private FileFilter triggerFilter;

    private final Object monitor = new Object();

    /**
     * Create a new {@link FileSystemWatcher2} instance.
     */
    public FileSystemWatcher() {
        this(true, DEFAULT_POLL_INTERVAL, DEFAULT_QUIET_PERIOD);
    }

    /**
     * Create a new {@link FileSystemWatcher2} instance.
     * 
     * @param daemon if a daemon thread used to monitor changes
     * @param pollInterval the amount of time to wait between checking for changes
     * @param quietPeriod the amount of time required after a change has been detected to ensure that updates have
     *        completed
     */
    public FileSystemWatcher(boolean daemon, long pollInterval, long quietPeriod) {
        Assert.isTrue(pollInterval > 0, "PollInterval must be positive");
        Assert.isTrue(quietPeriod > 0, "QuietPeriod must be positive");
        Assert.isTrue(pollInterval > quietPeriod, "PollInterval must be greater than QuietPeriod");
        this.daemon = daemon;
        this.pollInterval = pollInterval;
        this.quietPeriod = quietPeriod;
    }

    /**
     * Add listener for file change events. Cannot be called after the watcher has been {@link #start() started}.
     * 
     * @param fileChangeListener the listener to add
     */
    public void addListener(FileChangeListener fileChangeListener) {
        Assert.notNull(fileChangeListener, "FileChangeListener must not be null");
        synchronized (this.monitor) {
            checkNotStarted();
            this.listeners.add(fileChangeListener);
        }
    }

    /**
     * Add source folders to monitor. Cannot be called after the watcher has been {@link #start() started}.
     * 
     * @param folders the folders to monitor
     */
    public void addSourceFolders(Iterable<File> folders) {
        Assert.notNull(folders, "Folders must not be null");
        synchronized (this.monitor) {
            for (File folder : folders) {
                addSourceFolder(folder);
            }
        }
    }

    /**
     * Add a source folder to monitor. Cannot be called after the watcher has been {@link #start() started}.
     * 
     * @param folder the folder to monitor
     */
    public void addSourceFolder(File folder) {
        Assert.notNull(folder, "Folder must not be null");
        Assert.isTrue(folder.isDirectory(), "Folder '" + folder + "' must exist and must" + " be a directory");
        synchronized (this.monitor) {
            checkNotStarted();
            this.folders.put(folder, null);
        }
    }

    /**
     * Set an optional {@link FileFilter} used to limit the files that trigger a change.
     * 
     * @param triggerFilter a trigger filter or null
     */
    public void setTriggerFilter(FileFilter triggerFilter) {
        synchronized (this.monitor) {
            this.triggerFilter = triggerFilter;
        }
    }

    private void checkNotStarted() {
        synchronized (this.monitor) {
            Assert.state(this.watchThread == null, "FileSystemWatcher already started");
        }
    }

    /**
     * Start monitoring the source folder for changes.
     */
    public void start() {

        synchronized (this.monitor) {
            saveInitialSnapshots();
            if (this.watchThread == null) {
                Map<File, FolderSnapshot> localFolders = new HashMap<File, FolderSnapshot>();
                localFolders.putAll(this.folders);
                this.watchThread =
                    new Thread(new Watcher(this.remainingScans, new ArrayList<FileChangeListener>(this.listeners),
                        this.triggerFilter, this.pollInterval, this.quietPeriod, localFolders));
                this.watchThread.setName("File Watcher");
                this.watchThread.setDaemon(this.daemon);
                this.watchThread.start();
                System.out.println("sdfsdf");
            }
        }
    }

    private void saveInitialSnapshots() {
        for (File folder : this.folders.keySet()) {
            this.folders.put(folder, new FolderSnapshot(folder));
        }
    }

    /**
     * Stop monitoring the source folders.
     */
    public void stop() {
        stopAfter(0);
    }

    /**
     * Stop monitoring the source folders.
     * 
     * @param remainingScans the number of remaining scans
     */
    void stopAfter(int remainingScans) {
        Thread thread = null;
        synchronized (this.monitor) {
            thread = this.watchThread;
            if (thread != null) {
                this.remainingScans.set(remainingScans);
                if (remainingScans <= 0) {
                    thread.interrupt();
                }
            }
            this.watchThread = null;
        }
        if (thread != null && Thread.currentThread() != thread) {
            try {
                thread.join();
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }

    private static final class Watcher implements Runnable {

        private final AtomicInteger remainingScans;

        private final List<FileChangeListener> listeners;

        private final FileFilter triggerFilter;

        private final long pollInterval;

        private final long quietPeriod;

        private String modifitime;

        private final String apphome;

        private final SimpleDateFormat sdFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:dd");

        // private final String appname = "spring-config-client-0.0.1.jar";

        private AtomicBoolean isrun = new AtomicBoolean(true);

        private Map<File, FolderSnapshot> folders;

        private Watcher(AtomicInteger remainingScans, List<FileChangeListener> listeners, FileFilter triggerFilter,
            long pollInterval, long quietPeriod, Map<File, FolderSnapshot> folders) {
            this.remainingScans = remainingScans;
            this.listeners = listeners;
            this.triggerFilter = triggerFilter;
            this.pollInterval = pollInterval;
            this.quietPeriod = quietPeriod;
            this.folders = folders;
            System.out.println(JarTool.getJarPath());
            // this.apphome = JarTool.getJarPath().substring(0, JarTool.getJarPath().indexOf("file"));
            this.apphome = intnit();

            // intnit();
        }

        private String intnit() {

//             return null;
            return JarTool.getJarPath().substring(0, JarTool.getJarPath().indexOf("file"));

        }

        @Override
        public void run() {
            int remainingScans = this.remainingScans.get();
            while (remainingScans > 0 || remainingScans == -1) {
                try {
                    if (remainingScans > 0) {
                        this.remainingScans.decrementAndGet();
                    }
                    scan();

                } catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
                remainingScans = this.remainingScans.get();
            }
        };

        private void scan() throws InterruptedException {
//             LOGGER.info(pollInterval + "<--------------->" + quietPeriod);

            Thread.sleep(this.pollInterval - this.quietPeriod);
            Map<File, FolderSnapshot> previous;
            Map<File, FolderSnapshot> current = this.folders;
            // do {
            // previous = current;
            // current = getCurrentSnapshots();
            // Thread.sleep(this.quietPeriod);
            // }
            // while (isDifferent(previous, current));
            // if (isDifferent(this.folders, current)) {
            // updateSnapshots(current.values());
            // }
            checkReboot();

        }

        private void checkReboot() {

            String appname = "spring-config-client-0.0.1.jar";

            // String filepath = apphome + appname;
            File file = new File(apphome + appname);
            // 获取修改时间
            long lastModifitime = file.lastModified();
            String retStrFormatNowDate = sdFormatter.format(lastModifitime);

            LOGGER.info(modifitime + "<------------->" + retStrFormatNowDate);
            if (LOGGER.isDebugEnabled())
                LOGGER.debug(modifitime + "<--------------->" + retStrFormatNowDate);

            if (isDifferentTime(modifitime, retStrFormatNowDate) && modifitime != null && isrun.get()) {

                LOGGER.info("isDifferentTime");
                isrun = new AtomicBoolean(false);
                // reboot(appname);

                fireListeners();
                // if (file.renameTo(new File(apphome + "app.jar"))) {
                // // if (file.canWrite()) {
                // // try {
                // // listDicTory2(new File(apphome + "spring-config-client-0.0.1.jar_bak"), new File(apphome +
                // // appname));
                // // } catch (IllegalAccessException e) {
                // // LOGGER.error("sddddfsdf", e);
                // //
                // // // TODO Auto-generated catch block
                // // e.printStackTrace();
                // //
                // // } catch (IOException e) {
                // // LOGGER.error("sdfsdf", e);
                // //
                // // // TODO Auto-generated catch block
                // // e.printStackTrace();
                // //
                // // }
                //
                // if (new File(apphome + "app.jar").exists()) {
                // isrun = new AtomicBoolean(false);
                //
                // try {
                // listDicTory2(new File(apphome + "app.jar"), new File(apphome + appname));
                // } catch (IllegalAccessException e) {
                // LOGGER.error("sddddfsdf", e);
                //
                // // TODO Auto-generated catch block
                // e.printStackTrace();
                //
                // } catch (IOException e) {
                // LOGGER.error("sdfsdf", e);
                //
                // // TODO Auto-generated catch block
                // e.printStackTrace();
                //
                // }
                // reboot();
                // isrun = new AtomicBoolean(false);
                // }
                // LOGGER.info("11不可写入，不能提交！");
                // // file.
                // } else {
                // LOGGER.info("11可写入，可以提交！");
                //
                // if (new File(apphome + appname).exists()) {
                // reboot();
                // isrun = new AtomicBoolean(false);
                // }
                //
                // }
                // reboot();

                // isrun = false;
            } else {
                modifitime = retStrFormatNowDate;
            }

            // System.out.println("------000----44--------" + retStrFormatNowDate);
            //
            // String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getFile();
            //
            // String path = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
            // System.out.println("------------------");
            // this.getClass().
            // LOGGER.info(path);
            // LOGGER.info(JarTool.getJarDir());
            //
            // LOGGER.info(JarTool.getJarPath());
            // LOGGER.info(JarTool.getJarName());
            // LOGGER.info(System.getProperty("java.class.path"));
            // String apphome = JarTool.getJarPath().substring(0, JarTool.getJarPath().indexOf("file"));
            // LOGGER.info("apphome" + apphome);
            //
            // System.getProperty("java.class.path");
            //
            // System.out.println(path);

        }

        public static void listDicTory2(File file, File file2) throws IllegalAccessException, IOException {
            FileReader fr = new FileReader(file);
            FileWriter fw = new FileWriter(file2, false);// true:不覆盖原来内容,false:覆盖原来内容
            int c;
            while ((c = fr.read()) != -1) {
                fw.write((char)c);
                fw.flush();
            }
            fr.close();
            fw.close();
        }

        private boolean reboot(String appname) {
            try {
                LOGGER.info("reboot");
                if (isOSWindows()) {
                    LOGGER.info("Windows");
                } else {
                    LOGGER.info("linux");

                    // exeCmd = "/bin/sh -c startWeb.sh";
                    // String command = "find " + source.getRoute() + " -name '" + source.getName();
                    // spring-boot restart /usr/local/quickpf/config_client/spring-config-client-0.0.1.jar

                    String shpath = apphome + "restart.sh";
                    // LOGGER.info(shpath);
                    Process process = Runtime.getRuntime().exec(shpath);

                    // Process process = Runtime.getRuntime().exec("spring-boot restart " + apphome + appname);

                    LOGGER.info(apphome + appname);

                    // Process process =
                    // Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", "spring-boot restart " +apphome+
                    // appname});

                    printMessage(process.getInputStream());
                    printMessage(process.getErrorStream());
                    int value = process.waitFor();
                    LOGGER.info(String.valueOf(value));
                }
            } catch (Exception e) {
                LOGGER.error("sdsd", e);
                e.printStackTrace();
            }

            return false;
        }

        private boolean isDifferentTime(String previous, String current) {
            if (!current.equals(previous)) {
                return true;
            }

            return false;
        }

        private static void printMessage(final InputStream input) {
            new Thread(new Runnable() {
                public void run() {
                    //
                    Reader reader = new InputStreamReader(input);
                    BufferedReader bf = new BufferedReader(reader);
                    String line = null;
                    try {
                        while ((line = bf.readLine()) != null) {
                            System.out.println(line);
                            LOGGER.info(line);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

        public static boolean isOSWindows() {
            String osName = System.getProperty("os.name");
            if (Strings.isNullOrEmpty(osName)) {
                return false;
//                AbstractJerseyEurekaHttpClient
//                DefaultClientConnection
//                ScriptFactoryPostProcessor
            }
            return osName.startsWith("Windows");
        }

        private boolean isDifferent(Map<File, FolderSnapshot> previous, Map<File, FolderSnapshot> current) {
            if (!previous.keySet().equals(current.keySet())) {
                return true;
            }
            for (Map.Entry<File, FolderSnapshot> entry : previous.entrySet()) {
                FolderSnapshot previousFolder = entry.getValue();
                FolderSnapshot currentFolder = current.get(entry.getKey());
                if (!previousFolder.equals(currentFolder, this.triggerFilter)) {
                    return true;
                }
            }
            return false;
        }

        private Map<File, FolderSnapshot> getCurrentSnapshots() {
            Map<File, FolderSnapshot> snapshots = new LinkedHashMap<File, FolderSnapshot>();
            for (File folder : this.folders.keySet()) {
                snapshots.put(folder, new FolderSnapshot(folder));
            }
            return snapshots;
        }

        private void updateSnapshots(Collection<FolderSnapshot> snapshots) {
            Map<File, FolderSnapshot> updated = new LinkedHashMap<File, FolderSnapshot>();
            Set<ChangedFiles> changeSet = new LinkedHashSet<ChangedFiles>();
            for (FolderSnapshot snapshot : snapshots) {
                FolderSnapshot previous = this.folders.get(snapshot.getFolder());
                updated.put(snapshot.getFolder(), snapshot);
                ChangedFiles changedFiles = previous.getChangedFiles(snapshot, this.triggerFilter);
                if (!changedFiles.getFiles().isEmpty()) {
                    changeSet.add(changedFiles);
                }
            }
            if (!changeSet.isEmpty()) {
                fireListeners(Collections.unmodifiableSet(changeSet));
            }
            this.folders = updated;
        }

        private void fireListeners(Set<ChangedFiles> changeSet) {
            for (FileChangeListener listener : this.listeners) {
                listener.onChange(changeSet);
            }
        }

        private void fireListeners() {
            for (FileChangeListener listener : this.listeners) {
                listener.onChange(null);
            }
        }

    }

}
